{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Kernel Memory plugin for Semantic Kernel\n",
    "\n",
    "This notebook shows the basic usage of Kernel Memory as a Semantic Kernel Plugin.\n",
    "\n",
    "First of all, install the Kernel Memory SK Plugin and SK dependencies."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>Microsoft.KernelMemory.SemanticKernelPlugin, 0.29.240219.2</span></li><li><span>Microsoft.SemanticKernel, 1.5.0</span></li></ul></div></div>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#r \"nuget: Microsoft.SemanticKernel, 1.5.0\"\n",
    "#r \"nuget: Microsoft.KernelMemory.SemanticKernelPlugin, 0.29.240219.2\"\n",
    "\n",
    "using Microsoft.SemanticKernel;\n",
    "using Microsoft.KernelMemory;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Configuration\n",
    "\n",
    "For our demo, we use also the \"dotenv\" nuget, to load our secret credentials from a `.env` file.\n",
    "Make sure you create your `.env` file, with your OpenAI API Key, and your Memory Service API Key (if you set one).\n",
    "\n",
    "> ```\n",
    "> OPENAI_API_KEY=<your OpenAI API key>\n",
    "> MEMORY_API_KEY=<your KM web service API key>\n",
    "> ```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>dotenv.net, 3.1.3</span></li></ul></div></div>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#r \"nuget: dotenv.net, 3.1.3\"\n",
    "\n",
    "dotenv.net.DotEnv.Load();\n",
    "var env = dotenv.net.DotEnv.Read();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's setup Semantic Kernel as usual:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Semantic Kernel ready.\n"
     ]
    }
   ],
   "source": [
    "var kernel = Kernel.CreateBuilder()\n",
    "    .AddOpenAIChatCompletion(\n",
    "        modelId: \"gpt-3.5-turbo\",\n",
    "        apiKey: env[\"OPENAI_API_KEY\"])\n",
    "    .Build();\n",
    "\n",
    "Console.WriteLine(\"Semantic Kernel ready.\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Import Memory Plugin into SK\n",
    "\n",
    "Let's load Kernel Memory plugin into SK.\n",
    "\n",
    "Remember to start the memory service on localhost, otherwise change the URL and\n",
    "point it to your Kernel Memory service endpoint. In that case you should also\n",
    "provide the API key protecting your KM deployment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Memory plugin imported.\n"
     ]
    }
   ],
   "source": [
    "// This is the endpoint where you're running the memory service\n",
    "// Note: the TCP port might be different, depending on how you run it.\n",
    "var memoryServiceEndpoint = \"http://127.0.0.1:9001/\";\n",
    "\n",
    "// and the API Key you've configured. env[\"MEMORY_API_KEY\"] contains the value from the .env file\n",
    "var memoryServiceAPIKey = env[\"MEMORY_API_KEY\"];\n",
    "\n",
    "// Instance of the HTTP client used to talk to KM service\n",
    "var memoryConnector = new MemoryWebClient(memoryServiceEndpoint, memoryServiceAPIKey);\n",
    "\n",
    "// Name of the plugin. This is the name you'll use in skPrompt, e.g. {{memory.ask ...}}\n",
    "var pluginName = \"memory\";\n",
    "\n",
    "// Import the plugin into the kernel.\n",
    "// 'waitForIngestionToComplete' set to true forces memory write operations to wait for completion.\n",
    "var memoryPlugin = kernel.ImportPluginFromObject(\n",
    "    new MemoryPlugin(memoryConnector, waitForIngestionToComplete: true),\n",
    "    pluginName);\n",
    "\n",
    "Console.WriteLine(\"Memory plugin imported.\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Populate memory with sample information\n",
    "\n",
    "Let's store some data in memory:\n",
    "\n",
    "1. storing some text, e.g. Wikipedia's description of Orion\n",
    "2. and importing a PDF document, e.g. a news from NASA about Orion spacecraft\n",
    "\n",
    "To import data you can use either the web client connector or the plugin, here we use both just for demo purposes.\n",
    "\n",
    "When working with SK Planners and Semantic Functions (like the one below) you should always\n",
    "use KM Plugin. You can also use the web client connector to create new plugins and customize\n",
    "how you interact with your memories, e.g. selecting indexes, using filters, customizing chunking, etc."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Memory updated.\n"
     ]
    }
   ],
   "source": [
    "// Save a string using the web client\n",
    "await memoryConnector.ImportTextAsync(\n",
    "    \"Orion is a prominent set of stars visible during winter in \" +\n",
    "    \"the northern celestial hemisphere. It is one of the 88 modern constellations; \" +\n",
    "    \"it was among the 48 constellations listed by the 2nd-century astronomer Ptolemy. \" +\n",
    "    \"It is named for a hunter in Greek mythology.\", \n",
    "    documentId: \"OrionDefinition\");\n",
    "\n",
    "// Save a PDF file using the plugin\n",
    "var context = new KernelArguments\n",
    "{\n",
    "    [MemoryPlugin.FilePathParam] = \"NASA-news.pdf\",\n",
    "    [MemoryPlugin.DocumentIdParam] = \"NASA001\"\n",
    "};\n",
    "await memoryPlugin[\"SaveFile\"].InvokeAsync(kernel, context);\n",
    "\n",
    "Console.WriteLine(\"Memory updated.\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Use Memory in a semantic function\n",
    "\n",
    "Here we define a simple semantic function, using `{{memory.ask}}` to\n",
    "fetch information from memory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Semantic Function ready.\n"
     ]
    }
   ],
   "source": [
    "var skPrompt = \"\"\"\n",
    "Question to Memory: {{$input}}\n",
    "\n",
    "Answer from Memory: {{memory.ask $input}}\n",
    "\n",
    "If the answer is empty say 'I don't know' otherwise reply with a preview of the answer,\n",
    "truncated to 15 words. Prefix with one emoji relevant to the content.\n",
    "\"\"\";\n",
    "\n",
    "var myFunction = kernel.CreateFunctionFromPrompt(skPrompt);\n",
    "\n",
    "Console.WriteLine(\"Semantic Function ready.\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's now interact with our memories using `myFunction` function, asking questions, leveraging KM and LLMs to generate answers.\n",
    "\n",
    "Our function internally uses KM Plugin to retrieve an answer, and then custom some semantic logic to decide how to format the output."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🚀 NASA announces new test version of Orion spacecraft for Artemis II mission recovery operations.\n"
     ]
    }
   ],
   "source": [
    "var answer = await myFunction.InvokeAsync(kernel,\n",
    "    \"any news from NASA about Orion?\");\n",
    "\n",
    "Console.WriteLine(answer);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🌌 The word 'Orion' refers to a constellation, a set of stars and a NASA spacecraft.\n"
     ]
    }
   ],
   "source": [
    "var answer = await myFunction.InvokeAsync(kernel,\n",
    "    \"usage of the word 'Orion'\");\n",
    "\n",
    "Console.WriteLine(answer);"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (C#)",
   "language": "C#",
   "name": ".net-csharp"
  },
  "language_info": {
   "name": "polyglot-notebook"
  },
  "polyglot_notebook": {
   "kernelInfo": {
    "defaultKernelName": "csharp",
    "items": [
     {
      "aliases": [],
      "languageName": "csharp",
      "name": "csharp"
     }
    ]
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
